本文同步更新於blog
需求一:KTV系統要按照新增到系統的時間,由舊到新,實作歌曲排行
<?php
namespace App\IteratorPattern\TopSong;
use DateTime;
class Song
{
    /**
     * @var string
     */
    protected $name;
    /**
     * @var string
     */
    protected $singer;
    /**
     * @var DateTime
     */
    protected $releaseDate;
    public function __construct(array $data)
    {
        $this->name = $data['name'];
        $this->singer = $data['singer'];
        $this->releaseDate = new DateTime($data['releaseDate']);
    }
    /**
     * @return string
     */
    public function getName()
    {
        return $this->name;
    }
    /**
     * @return string
     */
    public function getSinger()
    {
        return $this->singer;
    }
    /**
     * @return DateTime
     */
    public function getReleaseDate()
    {
        return $this->releaseDate;
    }
}
<?php
namespace App\IteratorPattern\TopSong;
class SongCollection
{
    /**
     * @var Song[]
     */
    protected $items = [];
    public function __construct(array $originalSongs)
    {
        $this->items = $this->generateSongs($originalSongs);
    }
    /**
     * @param array $originalSongs
     * @return Song[]
     */
    private function generateSongs($originalSongs)
    {
        $result = [];
        foreach ($originalSongs as $originalSong) {
            $result[] = new Song($originalSong);
        }
        return $result;
    }
    /**
     * @return Song[]
     */
    public function getItems()
    {
        return $this->items;
    }
    /**
     * @return array
     */
    public function list()
    {
        foreach ($this->items as $item) {
            $result[] = $item->getName();
        }
        return $result;
    }
}
SongCollection就是迭代器模式中的集合類別 (Aggregate / Collection)。
不過我們目前還沒實作PHP的IteratorAggregate介面。
而generateSongs()的目的,是為了將不同來源的歌曲資訊,
轉換成系統認識的Song類別。
<?php
namespace App\IteratorPattern\TopSong;
use App\IteratorPattern\TopSong\SongCollection;
class Program
{
    /**
     * @var SongCollection
     */
    protected $songCollection;
    public function __construct(array $songs)
    {
        $this->songCollection = new SongCollection($songs);
    }
    public function list()
    {
        return $this->songCollection->list();
    }
}
目前的list()方法,很單純只用到foreach而已。
接著用迭代器模式改寫它。
<?php
namespace App\IteratorPattern\TopSong;
use App\IteratorPattern\TopSong\SongCollection;
use Iterator;
class SongIterator implements Iterator
{
    /**
     * @var SongCollection
     */
    protected $collection;
    /**
     * @var int
     */
    private $position = 0;
    public function __construct(SongCollection $collection)
    {
        $this->collection = $collection;
    }
    /**
     * Return the current element
     *
     * @return Song
     */
    public function current()
    {
        return $this->collection->getItems()[$this->position];
    }
    /**
     * Return the key of the current element
     *
     * @return int
     */
    public function key()
    {
        return $this->position;
    }
    /**
     * Move forward to next element
     *
     * @return void
     */
    public function next()
    {
        $this->position++;
    }
    /**
     * Rewind the Iterator to the first element
     *
     * @return int
     */
    public function rewind()
    {
        $this->position = 0;
    }
    /**
     * Checks if current position is valid
     *
     * @return void
     */
    public function valid()
    {
        return isset($this->collection->getItems()[$this->position]);
    }
}
SongIterator就是迭代器模式中的迭代器類別 (Iterator)。
我們實作了PHP的Iterator介面。
必須實作current, key, next, rewind, valid方法,其目的都有寫在PHPDoc中。
而我們在建構式中將剛剛的SongCollection注入。
<?php
namespace App\IteratorPattern\TopSong;
use IteratorAggregate;
use Traversable;
class SongCollection implements IteratorAggregate
{
    /**
     * @var Song[]
     */
    protected $items = [];
    public function __construct(array $dataOfSongs)
    {
        $this->items = $this->generateSongs($dataOfSongs);
    }
    /**
     * @param array $dataOfSongs
     * @return Song[]
     */
    private function generateSongs($dataOfSongs)
    {
        foreach ($dataOfSongs as $dataOfSong) {
            $result[] = new Song($dataOfSong);
        }
        return $result;
    }
    /**
     * @return Song[]
     */
    public function getItems()
    {
        return $this->items;
    }
    public function getIterator(): Traversable
    {
        return new SongIterator($this);
    }
}
我們實作了PHP的IteratorAggregate介面。
getIterator()方法會將當前的SongCollection注入,並回傳SongIterator。
<?php
namespace App\IteratorPattern\TopSong;
use App\IteratorPattern\TopSong\SongCollection;
class Program
{
    /**
     * @var SongCollection
     */
    protected $songCollection;
    public function __construct(array $songs)
    {
        $this->songCollection = new SongCollection($songs);
    }
    public function list()
    {
        $iterator = $this->songCollection->getIterator();
        foreach ($iterator as $item) {
            $result[] = $item->getName();
        }
        return $result;
    }
}
需求二:按照新增到系統的時間,由新到舊,實作歌曲排行
<?php
namespace App\IteratorPattern\TopSong;
use IteratorAggregate;
use Traversable;
class SongCollection implements IteratorAggregate
{
    /**
     * @var array
     */
    protected $dataOfSongs;
    /**
     * @var Song[]
     */
    protected $items = [];
    public function __construct(array $dataOfSongs)
    {
        $this->dataOfSongs = $dataOfSongs;
        $this->items = $this->generateSongs($dataOfSongs);
    }
    /**
     * @param array $dataOfSongs
     * @return Song[]
     */
    private function generateSongs($dataOfSongs)
    {
        foreach ($dataOfSongs as $dataOfSong) {
            $result[] = new Song($dataOfSong);
        }
        return $result;
    }
    /**
     * @return Song[]
     */
    public function getItems()
    {
        return $this->items;
    }
    public function getIterator(): Traversable
    {
        return new SongIterator($this);
    }
    /**
     * @return static
     */
    public function reverse()
    {
        return new static(array_reverse($this->dataOfSongs));
    }
}
這邊的reverse()方法,會將原始資料倒序後,回傳一個新的SongCollection。
<?php
namespace App\IteratorPattern\TopSong;
use App\IteratorPattern\TopSong\SongCollection;
class Program
{
    /**
     * @var SongCollection
     */
    protected $songCollection;
    public function __construct(array $songs)
    {
        $this->songCollection = new SongCollection($songs);
    }
    public function list()
    {
        $iterator = $this->songCollection->getIterator();
        foreach ($iterator as $item) {
            $result[] = $item->getName();
        }
        return $result;
    }
    public function listReverse()
    {
        $iterator = $this->songCollection->reverse()->getIterator();
        foreach ($iterator as $item) {
            $result[] = $item->getName();
        }
        return $result;
    }
}
[單一職責原則]
將集合元素 (Song)、 集合類別 (SongCollection) 及 迭代器 (SongIterator) 的職責分離。
[開放封閉原則]
無論是修改集合元素,或是迭代順序,我們都不會改到所有的程式碼。
[介面隔離原則]
定義出集合類別介面與迭代器介面,讓兩者不會互相影響。
[依賴反轉原則]
透過集合類別介面與迭代器介面,確保有取得迭代器及foreach()的能力。
最後附上類別圖:
(註:若不熟悉 UML 類別圖,可參考UML類別圖說明。)
ʕ •ᴥ•ʔ:一個讓我枯坐在翰林茶館兩個小時的模式(汗)。